iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 26
0
Modern Web

JavaScript Note系列 第 26

callback function

  • 分享至 

  • xImage
  •  

這邊要特別說明callback function,它相當重要,尤其是應用在同步/非同步的時候。

當一個函式作為引數的時候,在運算過程會由另一個函式來呼叫它,說穿了,callback function跟一般的函式都一樣,只是呼叫的方式不同而已。

常使用的事件監聽或setTimeout( )函式,就是最典型的例子,當事件觸發或時間一到,就會呼叫相對應的函式作處理。

document.addEventListener('click', function () {
    console.log('Mouse Click!');
});
setTimeout(function () {
    console.log('Hello World');
}, 1000);

以上的callback function是匿名函式,也就是沒有函式名稱,因為整個函式定義都被包含在另一個函式的參數列中,所以無需命名。

callback function也可以定義在全域執行環境中。

document.addEventListener('click', onclick);
function onclick() {
    console.log('Mouse Click!');
}
setTimeout(greeting, 1000);
function greeting() {
    console.log('Hello World!');
}

另一種形式的callback function

function funcA(callback) {
    callback();
}
function funcB() {
    console.log('Hello World');
}
funcA(funcB); //Hello World

funcA的的本體中,呼叫callback( ),意味著如果我們傳入的是函式的話,那該函式將會被funcA呼叫,這邊放入其他型別的引數會引發錯誤,不過為了不讓各位混淆,就不寫判斷式了。

呼叫funcA,funcB被當作引數傳入,並執行,所以結果會印出Hello World。

以上的範例輸出的內容是寫死的,我們稍微修改一下。

function funcA(x,callback) {
    callback(x);
}
function funcB(val) {
    console.log(val);
}
funcA('Hello',funcB); //Hello

在funcA加入一個參數x,代表欲輸出的內容。callback不變,依舊是欲執行的函式。
再來,我們改用匿名函式。

function funcA(x, callback) {
    callback(x);
}
funcA('Hello', (val) => {
    console.log(val);
});

這邊使用箭頭函式,乍看之下,感覺有點複雜,再回去看看第一個callback function的例子,callback function整個函式定義都被包含在另一個函式的參數列中,比對上面的範例

(val) => {
    console.log(val);
}

這個函式不就是被定義在funcA的參數欄中嗎?

為何需要用callback function這種複雜的方式?
callback function很大的好處是,我們可以自己定義函式執行的順序。

或許你會有疑問,我們大可在全域環境中,一行一行的執行函式,它就會按照順序執行了阿,話說對了一半,那是因為我們還沒講到同步/非同的概念,這部分之後會講,因為這是一個很大的議題,必須得分批來闡述,才比較容易理解。

每次講到callback function,有經驗的工程師,8成都會想到(Callback Hell)經典圖,有興趣的可以google。

回到剛剛的範例,funcA執行完後,接著會執行funcB,是吧,那如果我有ABCD這四個函式,必須得確保按照順序執行呢?

function funcA(a, callback) {
    console.log('do funcA.....');
    callback(a);
}
function funcB(b, callback) {
    console.log('do funcB.....');
    callback(b);
}
function funcC(c, callback) {
    console.log('do funcC.....');
    callback(c);
}
function funcD(d, callback) {
    console.log('do funcD.....');
    callback(d);
}
funcA('回', (val1) => {
    console.log(val1);
    funcB('呼', (val2) => {
        console.log(val2);
        funcC('地', (val3) => {
            console.log(val3);
            funcD('獄', (val4) => {
                console.log(val4);
            })
        })
    })
});

https://ithelp.ithome.com.tw/upload/images/20181110/20112573XbmovOrioG.png
這樣的方式雖然可以確保函式會依ABCD順序執行,但可讀性跟維護性非常差,

來說明一下,它的執行順序。

  • funcA的第一個引數'回',會帶入參數a,(val1)箭頭函式是callback function。
  • 首先執行funcA的任務,輸出do funcA.....,再呼叫(val1)箭頭函式,帶入參數a,也就是'回'。
  • (val1)箭頭函式的任務,是輸出該參數的值,也就是'回',最後呼叫funcB,依此類推,直到console.log(val4)為止。

我們一共執行8個函式,有看出來嗎?箭頭函式也算喔。

單純依函是順序來看,順序是,funcA>(val1)> funcB>(val2)>funcC>(val3)>funcD>(val4)。

不過好消息是,ES6多了一個Promise物件,讓我們遠離Callback Hell,之後的章節會說明。


上一篇
class 類別
下一篇
sync 同步 & async 非同步
系列文
JavaScript Note31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言